package com.googlecode.afx.utils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.springframework.core.annotation.AnnotationUtils;

/**
 * Util-class for working with the reflection API.
 * 
 * @author Martin
 *
 */
public class ReflectionUtils extends org.springframework.util.ReflectionUtils{

	/**
	 * Finds all methods that are annotated by the given <tt>annotationClazz</tt>.
	 * 
	 * @param clazz
	 * @param annotationClazz
	 * @return
	 */
	public static Method findAnnotatedMethod(Class<?> clazz, Class<? extends Annotation> annotationClazz) {
		Method[] allMethods = getAllDeclaredMethods(clazz);
		if(allMethods == null || allMethods.length == 0) {
			return null;
		}
		for(Method method : allMethods) {
			 Annotation annotation = AnnotationUtils.findAnnotation(method, annotationClazz);
			 if(annotation != null) {
				 return method;
			 }
		}
		return null;
	}
	
	/**
	 * Extracts all fields annotated by <tt>annotationClazz</tt> in class <tt>clazz</tt> and returns a map.
	 * 
	 * @param clazz
	 * @param annotationClazz
	 * @return
	 */
	public static <T extends Annotation> Map<Field, T> findAnnotatedFields(Class<?> clazz, Class<T> annotationClazz) {
		Map<Field, T> fieldMap = new HashMap<Field, T>();
		Field[] fields = clazz.getDeclaredFields();
		if(fields != null) {
			for(Field field : fields) {
				T a = AnnotationUtils.getAnnotation(field, annotationClazz);
				if(a != null) {
					fieldMap.put(field, a);
				}
			}
		}
		return fieldMap;
	}
	
	/**
	 * Takes a list of <tt>instances</tt> and matches them to <tt>parameterTypes</tt> by bringing them in the 
	 * same order than the <tt>parameterTypes</tt>.
	 * 
	 * @param parameterTypes
	 * @param instances
	 * @return
	 */
	public static Object[] matchInstancesToParameterTypes(Class<?>[] parameterTypes, Object... instances) {
		Object[] args = new Object[parameterTypes != null ? parameterTypes.length : 0];
		if(parameterTypes != null) {
			for(int i = 0; i < parameterTypes.length; i++) {
				args[i] = determineInstanceByType(parameterTypes[i], instances);
			}
		}
		return args;
	}
	
	/**
	 * Determines an instance of a given <tt>type</tt> from a list of <tt>instances</tt>.
	 *  
	 * @param type
	 * @param instances
	 * @return
	 */
	private static Object determineInstanceByType(Class<?> type, Object... instances) {
		if(instances != null) {
			for(Object instance : instances) {
				if(type.isAssignableFrom(instance.getClass())) {
					return instance;
				}
			}
		}
		return null;
	}
	
}
